import { Nodes, dom } from '@knno/dom';
import { DATA } from '../symbol';

class RouterElement extends HTMLElement {
	connectedCallback() {
		this.dispatchEvent(new Event('connect'));
	}
	disconnectedCallback() {
		this.dispatchEvent(new Event('disconnect'));
	}
}
customElements.define('router-view', RouterElement);

export type Component = ((path: string) => Promise<Nodes> | Nodes) | Nodes;

export interface Rule {
	path: string;
	component: Component;
	prefixMatch?: boolean;
}

interface MatchItem {
	[DATA]?: { component: Component; prefixMatch: boolean };
	[index: string]: MatchItem;
}

function setRule(item: MatchItem, arr: string[], component: Component, prefixMatch: boolean) {
	if (arr.length > 0) {
		let k = item[arr[0]];
		if (!k) {
			k = {};
			item[arr[0]] = k;
		}
		setRule(k, arr.slice(1), component, prefixMatch);
	} else {
		item[DATA] = { component, prefixMatch };
	}
}

export class RouterView extends Nodes {
	[DATA]: MatchItem = {};

	constructor() {
		let rnd: () => void;
		super(
			dom
				.new('router-view')
				.on('connect', () => {
					rnd = this.render.bind(this);
					window.addEventListener('hashchange', rnd);
				})
				.on('disconnect', () => {
					window.removeEventListener('hashchange', rnd);
				})
		);
	}

	rules(rules: Rule[]) {
		this[DATA] = {};
		this.appendRules(rules);
		return this;
	}

	appendRules(rules: Rule[]) {
		let hash = window.location.hash;
		if (hash.startsWith('#')) {
			hash = hash.substring(1);
		}
		if (hash.endsWith('/')) {
			hash = hash.substring(0, hash.length - 1);
		}
		rules.forEach((r) => {
			let path = r.path;
			if (!path.startsWith('/')) {
				path = hash + '/' + path;
			}
			if (path.endsWith('/')) {
				path = path.substring(0, path.length - 1);
			}
			const arr = path.split('/');
			setRule(this[DATA], arr, r.component, r.prefixMatch ?? false);
		});
		this.render();
		return this;
	}

	render() {
		let hash = window.location.hash;
		if (hash.startsWith('#')) {
			hash = hash.substring(1);
		}
		if (hash.endsWith('/')) {
			hash = hash.substring(0, hash.length - 1);
		}
		const arr = hash.split('/');
		let item: MatchItem = this[DATA];
		let prefixItem: MatchItem | undefined;
		for (const p of arr) {
			let find = item[p];
			if (!find) {
				find = item['*'];
				if (!find) {
					item = find;
					break;
				} else {
					item = find;
				}
			} else {
				item = find;
			}
			if (item[DATA] && item[DATA].prefixMatch) {
				prefixItem = item;
			}
		}
		if (!item && prefixItem) {
			item = prefixItem;
		}
		if (item) {
			const comp = item[DATA];
			if (comp && typeof comp.component === 'function') {
				Promise.resolve(comp.component(hash)).then((value) => {
					this.html('').append(value);
				});
			} else if (comp && typeof comp.component !== 'function') {
				this.html('').append(comp.component);
			} else {
				this.html('');
			}
		} else {
			this.html('');
		}
	}
}

export function routerView(...className: string[]): RouterView {
	return new RouterView().addClass(...className);
}
